package trb.complexshield.voxel;


public class Raytrace {
    private VoxelGrid voxelGrid;
    private int x;
    private int y;
    private int z;
    private float dtDx;
    private float dtDy;
    private float dtDz;
    private float t;
    private int n;
    private int xInc;
    private int yInc;
    private int zInc;
    private float tNextY;
    private float tNextX;
    private float tNextZ;

    public Raytrace(VoxelGrid voxelGrid) {
        this.voxelGrid = voxelGrid;
    }

    public void raytrace(Line line, Output output) {
        initVariables(line);
        boolean wasInside = voxelGrid.getBitAtIndex(x, y, z);
        float wasT = 0f;
        while (true) {
            output.addIntersection(line, x, y, z, t);
            step();
            if (t > 1) {
                break;
            }
            boolean isInside = voxelGrid.getBitAtIndex(x, y, z);
            if (isInside != wasInside) {
                if (!isInside) {
                    output.addSegment(line, wasT, t);
                }

                wasT = t;
                wasInside = isInside;
            }
        }
        if (wasT < 1) {
            output.addIntersection(line, x, y, z, 1f);
        }
        if (wasInside && wasT < 1) {
            output.addSegment(line, wasT, 1);
        }
    }

    private void initVariables(Line line) {
        float x1 = line.p1.x;
        float x2 = line.p2.x;
        float y1 = line.p1.y;
        float y2 = line.p2.y;
        float z1 = line.p1.z;
        float z2 = line.p2.z;
        float dx = Math.abs(x2 - x1);
        float dy = Math.abs(y2 - y1);
        float dz = Math.abs(z2 - z1);

        x = (int) floor(x1);
        y = (int) floor(y1);
        z = (int) floor(z1);


        dtDx = 1f / dx;
        dtDy = 1f / dy;
        dtDz = 1f / dz;

        if (dx == 0) {
            xInc = 0;
            tNextX = dtDx; // infinity
        } else if (x2 > x1) {
            xInc = 1;
            n += ((int) floor(x2)) - x;
            tNextX = (floor(x1) + 1 - x1) * dtDx;
        } else {
            xInc = -1;
            n += x - (int) floor(x2);
            tNextX = (x1 - floor(x1)) * dtDx;
        }

        if (dy == 0) {
            yInc = 0;
            tNextY = dtDy; // infinity
        } else if (y2 > y1) {
            yInc = 1;
            n += (int) floor(y2) - y;
            tNextY = (floor(y1) + 1 - y1) * dtDy;
        } else {
            yInc = -1;
            n += y - (int) floor(y2);
            tNextY = (y1 - floor(y1)) * dtDy;
        }

        if (dz == 0) {
            zInc = 0;
            tNextZ = dtDz; // infinity
        } else if (z2 > z1) {
            zInc = 1;
            n += (int) floor(z2) - z;
            tNextZ = (floor(z1) + 1 - z1) * dtDz;
        } else {
            zInc = -1;
            n += z - (int) floor(z2);
            tNextZ = (z1 - floor(z1)) * dtDz;
        }
    }

    private float floor(float value) {
        return (float) Math.floor(value);
    }

    private void step() {
        if (tNextY < tNextX) {
            if (tNextZ < tNextY) {
                z += zInc;
                t = tNextZ;
                tNextZ += dtDz;
            } else {
                y += yInc;
                t = tNextY;
                tNextY += dtDy;
            }
        } else if (tNextZ < tNextX) {
            z += zInc;
            t = tNextZ;
            tNextZ += dtDz;
        } else {
            x += xInc;
            t = tNextX;
            tNextX += dtDx;
        }
    }

    public interface Output {
        void addIntersection(Line line, int x, int y, int z, float t);

        void addSegment(Line line, float wasT, float t);
    }

}
